#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

#include <stdlib.h>

#include <errno.h>


#define MAX_N 32

#define TIMEOUT  0
#define BUF_SIZE 1024*16


int handler(int fd_in, int fd_out, int idx)
{
	int ret = 1;
	char buf[ BUF_SIZE+1];
	fd_set read_set,write_set;
	
	FD_ZERO( &read_set);
	FD_SET ( fd_in, &read_set);
	
	int max = (fd_in > fd_out) ? fd_in : fd_out;
	
	select( max+1, &read_set, NULL, NULL, NULL);
	int read_count = read( fd_in, buf, BUF_SIZE);
	if ( read_count == -1)
	{
		if ( errno == EINTR)
			return 1;
		else
		{
			perror("Child read error");
			exit(0);
		}
		
	}
	if ( read_count == 0)
	{
		return 0;
	}

	
	int write_count = 0;
	do
	{
		FD_ZERO( &write_set);
		FD_SET ( fd_out,&write_set);
		
		select( max+1, NULL, &write_set, NULL, NULL);
		int tmp = write( fd_out, &buf[  write_count], read_count - write_count);
		
		if ( tmp == -1)
		{
			if ( errno == EINTR)
				continue;
			else
			{
				perror("Child write error");
				exit(0);
			}
		}
		write_count += tmp;
	}
	while( read_count != write_count);
	
	return 1;
}

int max_fd( int **fd_pipes, int N)
{
	int i;
	int max = -1;
	for ( i = 0; i < 2*N+1; i++)
	{
		if ( max < fd_pipes[i][0])
		{
			max = fd_pipes[i][0];
		}
		if ( max < fd_pipes[i][1])
		{
			max = fd_pipes[i][1];
		}
	}
	return max;
}





int main( int argc, char **argv)
{
	if ( argc != 3)
	{
		printf("Wrong arguments\n");
		exit(1);
	}
	
	int fd_in = open( (char*)argv[2],O_RDONLY , 0644 | O_NONBLOCK);
	if ( fd_in == -1)
	{
		printf("Can't open file: %s\n",(char*)argv[2]);
		exit(1);
	}
	int N = 1;
	if ( !sscanf( (char*)argv[1],"%d",&N) || N <= 0 )
	{
		printf("Wrong number\n");
		exit(1);
	}
	if ( N > MAX_N)
	{
		printf("N is too large %d\n",N);
		exit(1);
	}
	//////////////////////////////////////////////////////////
	int  **fd_pipes = malloc( (2*N + 1)*sizeof( int** ));
	int tmp;
	int i;
	for ( i = 0; i < 2*N+1; i++)
	{
		fd_pipes[ i] = malloc( 2*sizeof(int));
		pipe2( fd_pipes[ i], O_NONBLOCK);
	}
	
	close( fd_pipes[ 0][ 0]);
	fd_pipes[ 0][0] = fd_in;
	
	close ( fd_pipes[ 2*N][ 1]);
	fd_pipes[ 2*N][ 1] = 1; //stdout
	
	int max = max_fd( fd_pipes,N);
	//printf( "\tMax = %d",max);
	///////////////////////////////////////////////////////////
	for ( i = 0; i < N; i++)
	{
		int tmp = fork();
		if ( tmp == 0)
		{
			int j;
			for ( j = 0; j < N; j++)
			{
				if ( j != i)
				{
					close( fd_pipes[ 2*j    ][0]);
					close( fd_pipes[ 2*j    ][1]);
					close( fd_pipes[ 2*j+1  ][0]);
					close( fd_pipes[ 2*j+1  ][1]);
				}
				else
				{
					close( fd_pipes[ 2*j   ][1]);
					close( fd_pipes[ 2*j+1 ][0]);
				}
			}
			
			while( handler( fd_pipes[ 2*i ][0], fd_pipes[ 2*i+1][1], i))
			{}
			
			for ( i = 0; i < 2*N+1; i++)
			{
				free( fd_pipes[ i]);
				fd_pipes[ i] = NULL;
			}
			free( fd_pipes);
			fd_pipes = NULL;
			exit(0);
		}
	}
	///////////////////////////////////////////////////////////////
	for ( i = 0; i < N; i++)
	{
		close( fd_pipes[ 2*i  ][0]);
		close( fd_pipes[ 2*i+1][1]);
	}
	close( fd_pipes[ 0][1]);
	close( fd_pipes[ 2*N][0]);
	///////////////////////////////////////////////////////////////
	tmp = 1;
	char **buffers               = malloc ( N*sizeof( char**));
	int   *buffers_size          = malloc ( N*sizeof( int)   );
	int   *buffers_read_count    = malloc ( N*sizeof( int)   );
	int   *buffers_write_count   = malloc ( N*sizeof( int)   );
	int   *buffers_read_finished = malloc ( N*sizeof( int)   );
	int   *buffers_write_finished= malloc ( N*sizeof( int)   );
	for ( i = N-1; i >= 0; i--)
	{
		buffers_read_count   [ i] = 0;
		buffers_write_count  [ i] = 0;
		
		buffers_read_finished [ i] = 0;
		buffers_write_finished[ i] = 0;
		
		if ( i < N - 5)
		{
			int size = 100*1024;
			buffers[ i]      = malloc( size);
			buffers_size[ i] = size;
		}else
		{
			int size = tmp * 1024;
			buffers     [ i] = malloc( size);
			buffers_size[ i] = size;
		}
		tmp *=3;
	}
	///////////////////////////////////////////////////////////
	fd_set read_fd;
	fd_set write_fd;
	
	int T = 1;
	int read_finished = 0;

	while ( T)
	{		
		FD_ZERO( &read_fd);
		FD_ZERO( &write_fd);
		for ( i = 0; i < N; i++)
		{
			if ( ( buffers_read_count[ i] == 0) && ( buffers_read_finished[ i] == 0))
			{
				FD_SET  ( fd_pipes[ 2*i+1 ][0], &read_fd );
			}
			
			
			if ( (buffers_read_finished[ i] && !buffers_write_finished[ i]) || ( buffers_read_count[ i] != 0)   && ( buffers_write_finished[i] == 0))
			{
				FD_SET  ( fd_pipes[ 2*i+2 ][1], &write_fd);
			}
		}
		
		if ( select( max+1, &read_fd, &write_fd, NULL, NULL) == -1)
		{
			perror("Parent select error");
		}
		
		for ( i = 0; i < N; i++)
		{
			if ( FD_ISSET( fd_pipes[ 2*i+1][0], &read_fd))
			{
				int read_count = read( fd_pipes[ 2*i+1][0], buffers[ i], buffers_size[ i]);
				if ( read_count == -1)
				{
					if ( errno == EINTR)
						continue;
					else
					{
						perror("Parent read error");
						exit(0);
					}
				}
				if ( read_count == 0 )
				{
					buffers_read_finished[ i] = 1;
					close( fd_pipes[ 2*i+1][0]);
				}
				buffers_read_count[ i] = read_count;
			}
			if (  FD_ISSET( fd_pipes[ 2*i+2][1], &write_fd))
			{
				int write_count =
				write(
					fd_pipes[ 2*i+2][1],
					&buffers[ i][buffers_write_count[ i]],
					buffers_read_count[ i] - buffers_write_count[ i]
				);
				if ( write_count == -1)
				{
					if ( errno == EINTR)
						continue;
					else
					{
						perror("Parent write error");
						exit(0);
					}
				}
				buffers_write_count[ i] += write_count;
				if ( buffers_read_count[ i] == buffers_write_count[ i])
				{
					buffers_read_count [ i] = 0;
					buffers_write_count[ i] = 0;
					if ( buffers_read_finished[ i])
					{
						close( fd_pipes[ 2*i+2][1]);
						buffers_write_finished[ i] = 1;
					}
				}
			}
		}
		
		T = 1;
		for ( i = 0; i < N; i++)
		{
			T = T && buffers_read_finished[ i] && buffers_write_finished[ i];
		}
		T = !T;
	}
	/////////////////////////////////////////////////////////
	for ( i = 0; i < N; i++)
	{
		free( buffers [ i]    );
		free( fd_pipes[ 2*i]  );
		free( fd_pipes[ 2*i+1]);
		
	}
	free( fd_pipes);
	
	free( buffers);
	free( buffers_size);
	free( buffers_read_count);
	free( buffers_write_count);
	
	free( buffers_read_finished);
	free( buffers_write_finished);
	
	exit(0);
}
